using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;

using Microsoft.Practices.CompositeWeb.Utility;

using WCSFContrib.Extensions.EventBroker;

namespace WCSFContrib.Extensions.EventBroker
{
	/// <summary>
	/// A Generic Mediator implementation which will mediate publisher and subscriber events and methods between the two instances of type S and T.
	/// When both instances are set the mediation happens automatically. The implementation does not remove subscriptions when either instance is disposed.
	/// </summary>
	/// <typeparam name="S">The one type.</typeparam>
	/// <typeparam name="T">The other type.</typeparam>
	public class Mediator<S, T>
	{
		private bool haveS;
		private bool haveT;

		private S sModel;
		private T tModel;

		private MemberList<EventInfo> sEvents;
		private MemberList<MethodInfo> sHandlers;

		private MemberList<EventInfo> tEvents;
		private MemberList<MethodInfo> tHandlers;

		/// <summary>
		/// Creates a new instance of the generic mediator
		/// </summary>
		public Mediator()
		{
			haveS = false;
			haveT = false;

			Type sType = typeof(S);
			sEvents = GetEvents(sType);
			sHandlers = GetHandlers(sType);

			Type tType = typeof(T);
			tEvents = GetEvents(tType);
			tHandlers = GetHandlers(tType);
		}

		/// <summary>
		/// Creates a new instance of the generic mediator with the instances to mediate passed in.
		/// </summary>
		/// <param name="sModel">The S instance.</param>
		/// <param name="tModel">The T instance.</param>
		public Mediator(S sModel, T tModel)
			: this()
		{
			SModel = sModel;
			TModel = tModel;
		}

		/// <summary>
		/// Setter for the S instance.
		/// </summary>
		public S SModel
		{
			set
			{
				Guard.ArgumentNotNull(value, "SModel");
				haveS = true;
				sModel = value;
				Init();
			}
		}

		/// <summary>
		/// Setter for the T instance.
		/// </summary>
		public T TModel
		{
			set
			{
				Guard.ArgumentNotNull(value, "TModel");
				haveT = true;
				tModel = value;
				Init();
			}
		}

		/// <summary>
		/// Note: The Type metadata should be cashed globally for the application.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		private static MemberList<EventInfo> GetEvents(Type type)
		{
			EventInfo[] events = type.GetEvents();
			MemberList<EventInfo> list = new MemberList<EventInfo>();
			foreach (EventInfo info in events)
			{
				object[] attributes = info.GetCustomAttributes(typeof(EventPublicationAttribute), false);
				if (attributes != null && attributes.Length == 1)
				{
					EventPublicationAttribute attribute = (EventPublicationAttribute)attributes[0];
					list.Add(new MemberReference<EventInfo>(attribute.Topic, info));
				}
			}
			return list;
		}

		private static MemberList<MethodInfo> GetHandlers(Type type)
		{
			MethodInfo[] methods = type.GetMethods();
			MemberList<MethodInfo> list = new MemberList<MethodInfo>();
			foreach (MethodInfo info in methods)
			{
				object[] attributes = info.GetCustomAttributes(typeof(EventSubscriptionAttribute), false);
				if (attributes != null && attributes.Length >= 1)
					foreach (object attr in attributes)
					{
						EventSubscriptionAttribute attribute = (EventSubscriptionAttribute)attr;
						list.Add(new MemberReference<MethodInfo>(attribute.Topic, info));
					}
			}
			return list;
		}

		private void Init()
		{
			if (haveS && haveT)
				HookUp();
		}

		private void HookUp()
		{
			HookupHandlers(sModel, sHandlers, tModel, tEvents);
			HookupHandlers(tModel, tHandlers, sModel, sEvents);
		}

		private void HookupHandlers(object eventHandler, MemberList<MethodInfo> handlers,
											 object eventSender, MemberList<EventInfo> events)
		{
			foreach (List<MemberReference<MethodInfo>> handlerList in handlers)
			{
                foreach (MemberReference<MethodInfo> handler in handlerList)
                {
						 Subscriber subscriber = new Subscriber(eventHandler, new MemberReference<MethodInfo>(handler.Topic, handler.Info));
                    List<MemberReference<EventInfo>> eventInfos;
                    if (events.TryGet(handler.Topic, out eventInfos))
                    {
                        foreach (MemberReference<EventInfo> eventInf in eventInfos)
                        {
									Publisher publisher = new Publisher(eventSender, new MemberReference<EventInfo>(eventInf.Topic, eventInf.Info));
                            publisher.AddEventHandler(subscriber.GetDelegate(publisher.EventHandlerType));
                        }
                    }
                }
			}
		}

		private class MemberList<U> : KeyedCollection<string, List<MemberReference<U>>> where U : MemberInfo
		{
            protected override string GetKeyForItem(List<MemberReference<U>> item)
			{
				return GetKeyForItem(item[0]);
			}

            protected string GetKeyForItem(MemberReference<U> item)
            {
                return item.Topic;
            }

            public void Add(MemberReference<U> info)
            {
                List<MemberReference<U>> list;
                if (TryGet(GetKeyForItem(info), out list))
                {
                    list.Add(info);
                }
                else
                {
                    list = new List<MemberReference<U>>();
                    list.Add(info);
                    Add(list);
                }
            }

            public bool TryGet(string key, out List<MemberReference<U>> info)
			{
				if (Contains(key))
				{
					info = this[key];
					return true;
				}
				else
				{
					info = null;
					return false;
				}
			}
		}
	}
}